<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="resources/manual.js"></script>
  </head>
  <body>
    <p>
      These tests require a supported HID device:
    </p>
    <ul>
      <li>Sony DS4 Wireless Controller v2 (054c:09cc) [USB]
      <li>Sony DualSense Wireless Controller (054c:0ce6) [USB]
      <li>Nintendo Switch Pro Controller (0573:2009) [USB]
      <li>Philips Speech Mike III (0911:0fa0) [USB]
      <li>Jabra Evolve 65 Stereo Headset with Link (0b0e:0306) [USB]
      <li>Google Stadia Controller (18d1:9400) [USB]
    </ul>
    <script>
      // Formats a 32-bit integer |value| in hexadecimal with leading zeros.
      const hex32 = value => {
        return ('00000000' + value.toString(16)).substr(-8);
      };

      // Finds the item in |report| with matching |usage|, computes its bit
      // index within the report map, and checks against the |expectedBitIndex|
      // and |expectedBitWidth|. If a usage is reused for multiple fields within
      // a report, only the first field is considered.
      const checkReportUsage =
          (report, usage, expectedBitIndex, expectedBitWidth) => {
        const itemIndex = report.items.findIndex(item => {
          if (item.isRange)
            return item.usageMinimum <= usage && usage <= item.usageMaximum;
          return item.usages.includes(usage);
        });
        assert_greater_than_equal(
            itemIndex, 0,
            'No report item matching usage 0x' + hex32(usage) + '.');
        if (itemIndex < 0)
          return null;

        let bitIndex = 0;
        for (let i = 0; i < itemIndex; ++i)
          bitIndex += report.items[i].reportSize * report.items[i].reportCount;

        const item = report.items[itemIndex];
        if (!item.isArray) {
          if (item.isRange) {
            bitIndex += (usage - item.usageMinimum) * item.reportSize;
          } else {
            const usageIndex = item.usages.indexOf(usage);
            bitIndex += usageIndex * item.reportSize;
          }
        }

        assert_equals(bitIndex, expectedBitIndex,
                      'Incorrect bit index for usage 0x' + hex32(usage) + '.');
        assert_equals(item.reportSize, expectedBitWidth,
                      'Incorrect bit width for usage 0x' + hex32(usage) + '.');
      };

      // Returns the first top-level collection in |device.collections| with a
      // usage matching |usagePage| and |usage|, or undefined if no matching
      // collection was found.
      const getCollectionByUsage = (device, usagePage, usage) => {
        return device.collections.find(c => {
            return c.usagePage == usagePage && c.usage == usage;});
      }

      // Returns the first device in |devices| with a top-level collection
      // matching |usagePage| and |usage|, or undefined if no matching device
      // was found.
      const getDeviceByCollectionUsage = (devices, usagePage, usage) => {
        return devices.find(d => {
          return getCollectionByUsage(d, usagePage, usage);
        }) !== undefined;
      }

      // Returns the first report in |devices| with matching |reportType| and
      // |reportId|, or undefined if no matching report was found.
      const getReport = (devices, reportType, reportId) => {
        for (const d of devices) {
          for (const c of d.collections) {
            let reports = [];
            if (reportType == 'input')
              reports = c.inputReports;
            else if (reportType == 'output')
              reports = c.outputReports;
            else if (reportType == 'feature')
              reports = c.featureReports;

            const r = reports.find(r => { return r.reportId == reportId; });
            if (r !== undefined)
              return r;
          }
        }
        return undefined;
      };

      // Returns true if |devices| contains a device with matching |vendorId|
      // and |productId|.
      const hasDeviceIds = (devices, vendorId, productId) => {
        return devices.find(d => {
          return d.vendorId == vendorId && d.productId == productId;
        }) !== undefined;
      };

      const checkReportMapDualshock4 = devices => {
        // Expect one device with one top-level collection.
        assert_equals(devices.length, 1, 'device count');
        assert_equals(devices[0].collections.length, 1, 'collection count');
        const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
        assert_not_equals(collection, undefined, 'game pad collection');

        // Input report
        const input01 = getReport(devices, 'input', 0x01);
        assert_not_equals(input01, undefined, 'input report 0x01');
        checkReportUsage(input01, 0x00010030, 0, 8);
        checkReportUsage(input01, 0x00010031, 8, 8);
        checkReportUsage(input01, 0x00010032, 16, 8);
        checkReportUsage(input01, 0x00010035, 24, 8);
        checkReportUsage(input01, 0x00010039, 32, 4);
        checkReportUsage(input01, 0x00090001, 36, 1);
        checkReportUsage(input01, 0x00090002, 37, 1);
        checkReportUsage(input01, 0x00090003, 38, 1);
        checkReportUsage(input01, 0x00090004, 39, 1);
        checkReportUsage(input01, 0x00090005, 40, 1);
        checkReportUsage(input01, 0x00090006, 41, 1);
        checkReportUsage(input01, 0x00090007, 42, 1);
        checkReportUsage(input01, 0x00090008, 43, 1);
        checkReportUsage(input01, 0x00090009, 44, 1);
        checkReportUsage(input01, 0x0009000a, 45, 1);
        checkReportUsage(input01, 0x0009000b, 46, 1);
        checkReportUsage(input01, 0x0009000c, 47, 1);
        checkReportUsage(input01, 0x0009000d, 48, 1);
        checkReportUsage(input01, 0x0009000e, 49, 1);
        checkReportUsage(input01, 0xff000020, 50, 6);
        checkReportUsage(input01, 0x00010033, 56, 8);
        checkReportUsage(input01, 0x00010034, 64, 8);

        // Output report
        const output05 = getReport(devices, 'output', 0x05);
        assert_not_equals(output05, undefined, 'output report 0x05');
        checkReportUsage(output05, 0xff000022, 0, 8);

        // Feature reports
        const feature02 = getReport(devices, 'feature', 0x02);
        assert_not_equals(feature02, undefined, 'feature report 0x02');
        checkReportUsage(feature02, 0xff000024, 0, 8);
        const feature04 = getReport(devices, 'feature', 0x04);
        assert_not_equals(feature04, undefined, 'feature report 0x04');
        checkReportUsage(feature04, 0xff000023, 0, 8);
        const feature08 = getReport(devices, 'feature', 0x08);
        assert_not_equals(feature08, undefined, 'feature report 0x08');
        checkReportUsage(feature08, 0xff000025, 0, 8);
        const feature10 = getReport(devices, 'feature', 0x10);
        assert_not_equals(feature10, undefined, 'feature report 0x10');
        checkReportUsage(feature10, 0xff000026, 0, 8);
        const feature11 = getReport(devices, 'feature', 0x11);
        assert_not_equals(feature11, undefined, 'feature report 0x11');
        checkReportUsage(feature11, 0xff000027, 0, 8);
        const feature12 = getReport(devices, 'feature', 0x12);
        assert_not_equals(feature12, undefined, 'feature report 0x12');
        checkReportUsage(feature12, 0xff020021, 0, 8);
        const feature13 = getReport(devices, 'feature', 0x13);
        assert_not_equals(feature13, undefined, 'feature report 0x13');
        checkReportUsage(feature13, 0xff020022, 0, 8);
        const feature14 = getReport(devices, 'feature', 0x14);
        assert_not_equals(feature14, undefined, 'feature report 0x14');
        checkReportUsage(feature14, 0xff050020, 0, 8);
        const feature15 = getReport(devices, 'feature', 0x15);
        assert_not_equals(feature15, undefined, 'feature report 0x15');
        checkReportUsage(feature15, 0xff050021, 0, 8);
        const feature80 = getReport(devices, 'feature', 0x80);
        assert_not_equals(feature80, undefined, 'feature report 0x80');
        checkReportUsage(feature80, 0xff800020, 0, 8);
        const feature81 = getReport(devices, 'feature', 0x81);
        assert_not_equals(feature81, undefined, 'feature report 0x81');
        checkReportUsage(feature81, 0xff800021, 0, 8);
        const feature82 = getReport(devices, 'feature', 0x82);
        assert_not_equals(feature82, undefined, 'feature report 0x82');
        checkReportUsage(feature82, 0xff800022, 0, 8);
        const feature83 = getReport(devices, 'feature', 0x83);
        assert_not_equals(feature83, undefined, 'feature report 0x83');
        checkReportUsage(feature83, 0xff800023, 0, 8);
        const feature84 = getReport(devices, 'feature', 0x84);
        assert_not_equals(feature84, undefined, 'feature report 0x84');
        checkReportUsage(feature84, 0xff800024, 0, 8);
        const feature85 = getReport(devices, 'feature', 0x85);
        assert_not_equals(feature85, undefined, 'feature report 0x85');
        checkReportUsage(feature85, 0xff800025, 0, 8);
        const feature86 = getReport(devices, 'feature', 0x86);
        assert_not_equals(feature86, undefined, 'feature report 0x86');
        checkReportUsage(feature86, 0xff800026, 0, 8);
        const feature87 = getReport(devices, 'feature', 0x87);
        assert_not_equals(feature87, undefined, 'feature report 0x87');
        checkReportUsage(feature87, 0xff800027, 0, 8);
        const feature88 = getReport(devices, 'feature', 0x88);
        assert_not_equals(feature88, undefined, 'feature report 0x88');
        checkReportUsage(feature88, 0xff800028, 0, 8);
        const feature89 = getReport(devices, 'feature', 0x89);
        assert_not_equals(feature89, undefined, 'feature report 0x89');
        checkReportUsage(feature89, 0xff800029, 0, 8);
        const feature90 = getReport(devices, 'feature', 0x90);
        assert_not_equals(feature90, undefined, 'feature report 0x90');
        checkReportUsage(feature90, 0xff800030, 0, 8);
        const feature91 = getReport(devices, 'feature', 0x91);
        assert_not_equals(feature91, undefined, 'feature report 0x91');
        checkReportUsage(feature91, 0xff800031, 0, 8);
        const feature92 = getReport(devices, 'feature', 0x92);
        assert_not_equals(feature92, undefined, 'feature report 0x92');
        checkReportUsage(feature92, 0xff800032, 0, 8);
        const feature93 = getReport(devices, 'feature', 0x93);
        assert_not_equals(feature93, undefined, 'feature report 0x93');
        checkReportUsage(feature93, 0xff800033, 0, 8);
        const feature94 = getReport(devices, 'feature', 0x94);
        assert_not_equals(feature94, undefined, 'feature report 0x94');
        checkReportUsage(feature94, 0xff800034, 0, 8);
        const featurea0 = getReport(devices, 'feature', 0xa0);
        assert_not_equals(featurea0, undefined, 'feature report 0xa0');
        checkReportUsage(featurea0, 0xff800040, 0, 8);
        const featurea1 = getReport(devices, 'feature', 0xa1);
        assert_not_equals(featurea1, undefined, 'feature report 0xa1');
        checkReportUsage(featurea1, 0xff800041, 0, 8);
        const featurea2 = getReport(devices, 'feature', 0xa2);
        assert_not_equals(featurea2, undefined, 'feature report 0xa2');
        checkReportUsage(featurea2, 0xff800042, 0, 8);
        const featurea3 = getReport(devices, 'feature', 0xa3);
        assert_not_equals(featurea3, undefined, 'feature report 0xa3');
        checkReportUsage(featurea3, 0xff800043, 0, 8);
        const featurea4 = getReport(devices, 'feature', 0xa4);
        assert_not_equals(featurea4, undefined, 'feature report 0xa4');
        checkReportUsage(featurea4, 0xff800044, 0, 8);
        const featurea7 = getReport(devices, 'feature', 0xa7);
        assert_not_equals(featurea7, undefined, 'feature report 0xa7');
        checkReportUsage(featurea7, 0xff80004a, 0, 8);
        const featurea8 = getReport(devices, 'feature', 0xa8);
        assert_not_equals(featurea8, undefined, 'feature report 0xa8');
        checkReportUsage(featurea8, 0xff80004b, 0, 8);
        const featurea9 = getReport(devices, 'feature', 0xa9);
        assert_not_equals(featurea9, undefined, 'feature report 0xa9');
        checkReportUsage(featurea9, 0xff80004c, 0, 8);
        const featureaa = getReport(devices, 'feature', 0xaa);
        assert_not_equals(featureaa, undefined, 'feature report 0xaa');
        checkReportUsage(featureaa, 0xff80004e, 0, 8);
        const featureab = getReport(devices, 'feature', 0xab);
        assert_not_equals(featureab, undefined, 'feature report 0xab');
        checkReportUsage(featureab, 0xff80004f, 0, 8);
        const featureac = getReport(devices, 'feature', 0xac);
        assert_not_equals(featureac, undefined, 'feature report 0xac');
        checkReportUsage(featureac, 0xff800050, 0, 8);
        const featuread = getReport(devices, 'feature', 0xad);
        assert_not_equals(featuread, undefined, 'feature report 0xad');
        checkReportUsage(featuread, 0xff800051, 0, 8);
        const featureae = getReport(devices, 'feature', 0xae);
        assert_not_equals(featureae, undefined, 'feature report 0xae');
        checkReportUsage(featureae, 0xff800052, 0, 8);
        const featureaf = getReport(devices, 'feature', 0xaf);
        assert_not_equals(featureaf, undefined, 'feature report 0xaf');
        checkReportUsage(featureaf, 0xff800053, 0, 8);
        const featureb0 = getReport(devices, 'feature', 0xb0);
        assert_not_equals(featureb0, undefined, 'feature report 0xb0');
        checkReportUsage(featureb0, 0xff800054, 0, 8);
        const featureb3 = getReport(devices, 'feature', 0xb3);
        assert_not_equals(featureb3, undefined, 'feature report 0xb3');
        checkReportUsage(featureb3, 0xff800055, 0, 8);
        const featureb4 = getReport(devices, 'feature', 0xb4);
        assert_not_equals(featureb4, undefined, 'feature report 0xb4');
        checkReportUsage(featureb4, 0xff800055, 0, 8);
        const featureb5 = getReport(devices, 'feature', 0xb5);
        assert_not_equals(featureb5, undefined, 'feature report 0xb5');
        checkReportUsage(featureb5, 0xff800056, 0, 8);
        const featured0 = getReport(devices, 'feature', 0xd0);
        assert_not_equals(featured0, undefined, 'feature report 0xd0');
        checkReportUsage(featured0, 0xff800058, 0, 8);
        const featured4 = getReport(devices, 'feature', 0xd4);
        assert_not_equals(featured4, undefined, 'feature report 0xd4');
        checkReportUsage(featured4, 0xff800059, 0, 8);
        const featuree0 = getReport(devices, 'feature', 0xe0);
        assert_not_equals(featuree0, undefined, 'feature report 0xe0');
        checkReportUsage(featuree0, 0xff800057, 0, 8);
        const featuref0 = getReport(devices, 'feature', 0xf0);
        assert_not_equals(featuref0, undefined, 'feature report 0xf0');
        checkReportUsage(featuref0, 0xff800047, 0, 8);
        const featuref1 = getReport(devices, 'feature', 0xf1);
        assert_not_equals(featuref1, undefined, 'feature report 0xf1');
        checkReportUsage(featuref1, 0xff800048, 0, 8);
        const featuref2 = getReport(devices, 'feature', 0xf2);
        assert_not_equals(featuref2, undefined, 'feature report 0xf2');
        checkReportUsage(featuref2, 0xff800049, 0, 8);
      };

      checkReportMapDualSense = devices => {
        // Expect one device with one top-level collection.
        assert_equals(devices.length, 1, 'device count');
        assert_equals(devices[0].collections.length, 1, 'collection count');
        const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
        assert_not_equals(collection, undefined, 'game pad collection');

        // Input report
        const input01 = getReport(devices, 'input', 0x01);
        assert_not_equals(input01, undefined, 'input report 0x01');
        checkReportUsage(input01, 0x00010030, 0, 8);
        checkReportUsage(input01, 0x00010031, 8, 8);
        checkReportUsage(input01, 0x00010032, 16, 8);
        checkReportUsage(input01, 0x00010035, 24, 8);
        checkReportUsage(input01, 0x00010033, 32, 8);
        checkReportUsage(input01, 0x00010034, 40, 8);
        checkReportUsage(input01, 0xff000020, 48, 8);
        checkReportUsage(input01, 0x00010039, 56, 4);
        checkReportUsage(input01, 0x00090001, 60, 1);
        checkReportUsage(input01, 0x00090002, 61, 1);
        checkReportUsage(input01, 0x00090003, 62, 1);
        checkReportUsage(input01, 0x00090004, 63, 1);
        checkReportUsage(input01, 0x00090005, 64, 1);
        checkReportUsage(input01, 0x00090006, 65, 1);
        checkReportUsage(input01, 0x00090007, 66, 1);
        checkReportUsage(input01, 0x00090008, 67, 1);
        checkReportUsage(input01, 0x00090009, 68, 1);
        checkReportUsage(input01, 0x0009000a, 69, 1);
        checkReportUsage(input01, 0x0009000b, 70, 1);
        checkReportUsage(input01, 0x0009000c, 71, 1);
        checkReportUsage(input01, 0x0009000d, 72, 1);
        checkReportUsage(input01, 0x0009000e, 73, 1);
        checkReportUsage(input01, 0x0009000f, 74, 1);
        checkReportUsage(input01, 0xff000021, 75, 1);
        checkReportUsage(input01, 0xff000022, 88, 8);

        // Output report
        const output02 = getReport(devices, 'output', 0x02);
        assert_not_equals(output02, undefined, 'output report 0x02');
        checkReportUsage(output02, 0xff000023, 0, 8);

        // Feature reports
        const feature05 = getReport(devices, 'feature', 0x05);
        assert_not_equals(feature05, undefined, 'feature report 0x05');
        checkReportUsage(feature05, 0xff000033, 0, 8);
        const feature08 = getReport(devices, 'feature', 0x08);
        assert_not_equals(feature08, undefined, 'feature report 0x08');
        checkReportUsage(feature08, 0xff000034, 0, 8);
        const feature09 = getReport(devices, 'feature', 0x09);
        assert_not_equals(feature09, undefined, 'feature report 0x09');
        checkReportUsage(feature09, 0xff000024, 0, 8);
        const feature0a = getReport(devices, 'feature', 0x0a);
        assert_not_equals(feature0a, undefined, 'feature report 0x0a');
        checkReportUsage(feature0a, 0xff000025, 0, 8);
        const feature20 = getReport(devices, 'feature', 0x20);
        assert_not_equals(feature20, undefined, 'feature report 0x20');
        checkReportUsage(feature20, 0xff000026, 0, 8);
        const feature21 = getReport(devices, 'feature', 0x21);
        assert_not_equals(feature21, undefined, 'feature report 0x21');
        checkReportUsage(feature21, 0xff000027, 0, 8);
        const feature22 = getReport(devices, 'feature', 0x22);
        assert_not_equals(feature22, undefined, 'feature report 0x22');
        checkReportUsage(feature22, 0xff000040, 0, 8);
        const feature80 = getReport(devices, 'feature', 0x80);
        assert_not_equals(feature80, undefined, 'feature report 0x80');
        checkReportUsage(feature80, 0xff000028, 0, 8);
        const feature81 = getReport(devices, 'feature', 0x81);
        assert_not_equals(feature81, undefined, 'feature report 0x81');
        checkReportUsage(feature81, 0xff000029, 0, 8);
        const feature82 = getReport(devices, 'feature', 0x82);
        assert_not_equals(feature82, undefined, 'feature report 0x82');
        checkReportUsage(feature82, 0xff00002a, 0, 8);
        const feature83 = getReport(devices, 'feature', 0x83);
        assert_not_equals(feature83, undefined, 'feature report 0x83');
        checkReportUsage(feature83, 0xff00002b, 0, 8);
        const feature84 = getReport(devices, 'feature', 0x84);
        assert_not_equals(feature84, undefined, 'feature report 0x84');
        checkReportUsage(feature84, 0xff00002c, 0, 8);
        const feature85 = getReport(devices, 'feature', 0x85);
        assert_not_equals(feature85, undefined, 'feature report 0x85');
        checkReportUsage(feature85, 0xff00002d, 0, 8);
        const featurea0 = getReport(devices, 'feature', 0xa0);
        assert_not_equals(featurea0, undefined, 'feature report 0xa0');
        checkReportUsage(featurea0, 0xff00002e, 0, 8);
        const featuree0 = getReport(devices, 'feature', 0xe0);
        assert_not_equals(featuree0, undefined, 'feature report 0xe0');
        checkReportUsage(featuree0, 0xff00002f, 0, 8);
        const featuref0 = getReport(devices, 'feature', 0xf0);
        assert_not_equals(featuref0, undefined, 'feature report 0xf0');
        checkReportUsage(featuref0, 0xff000030, 0, 8);
        const featuref1 = getReport(devices, 'feature', 0xf1);
        assert_not_equals(featuref1, undefined, 'feature report 0xf1');
        checkReportUsage(featuref1, 0xff000031, 0, 8);
        const featuref2 = getReport(devices, 'feature', 0xf2);
        assert_not_equals(featuref2, undefined, 'feature report 0xf2');
        checkReportUsage(featuref2, 0xff000032, 0, 8);
        const featuref4 = getReport(devices, 'feature', 0xf4);
        assert_not_equals(featuref4, undefined, 'feature report 0xf4');
        checkReportUsage(featuref4, 0xff000035, 0, 8);
        const featuref5 = getReport(devices, 'feature', 0xf5);
        assert_not_equals(featuref5, undefined, 'feature report 0xf5');
        checkReportUsage(featuref5, 0xff000036, 0, 8);
      };

      checkReportMapSwitchPro = devices => {
        // Expect one device with one top-level collection.
        assert_equals(devices.length, 1, 'device count');
        assert_equals(devices[0].collections.length, 1, 'collection count');
        const collection = getCollectionByUsage(devices[0], 0x0001, 0x0004);
        assert_not_equals(collection, undefined, 'joystick collection');

        // Input reports
        const input30 = getReport(devices, 'input', 0x30);
        assert_not_equals(input30, undefined, 'input report 0x30');
        checkReportUsage(input30, 0x00090001, 0, 1);
        checkReportUsage(input30, 0x00090002, 1, 1);
        checkReportUsage(input30, 0x00090003, 2, 1);
        checkReportUsage(input30, 0x00090004, 3, 1);
        checkReportUsage(input30, 0x00090005, 4, 1);
        checkReportUsage(input30, 0x00090006, 5, 1);
        checkReportUsage(input30, 0x00090007, 6, 1);
        checkReportUsage(input30, 0x00090008, 7, 1);
        checkReportUsage(input30, 0x00090009, 8, 1);
        checkReportUsage(input30, 0x0009000a, 9, 1);
        checkReportUsage(input30, 0x0009000b, 10, 1);
        checkReportUsage(input30, 0x0009000c, 11, 1);
        checkReportUsage(input30, 0x0009000d, 12, 1);
        checkReportUsage(input30, 0x0009000e, 13, 1);
        checkReportUsage(input30, 0x00010030, 16, 16);
        checkReportUsage(input30, 0x00010031, 32, 16);
        checkReportUsage(input30, 0x00010032, 48, 16);
        checkReportUsage(input30, 0x00010035, 64, 16);
        checkReportUsage(input30, 0x00010039, 80, 4);
        checkReportUsage(input30, 0x0009000f, 84, 1);
        checkReportUsage(input30, 0x00090010, 85, 1);
        checkReportUsage(input30, 0x00090011, 86, 1);
        checkReportUsage(input30, 0x00090012, 87, 1);

        const input21 = getReport(devices, 'input', 0x21);
        assert_not_equals(input21, undefined, 'input report 0x21');
        checkReportUsage(input21, 0xff000001, 0, 8);

        const input81 = getReport(devices, 'input', 0x81);
        assert_not_equals(input81, undefined, 'input report 0x81');
        checkReportUsage(input81, 0xff000002, 0, 8);

        // Output reports
        const output01 = getReport(devices, 'output', 0x01);
        assert_not_equals(output01, undefined, 'output report 0x01');
        checkReportUsage(output01, 0xff000003, 0, 8);

        const output10 = getReport(devices, 'output', 0x10);
        assert_not_equals(output10, undefined, 'output report 0x10');
        checkReportUsage(output10, 0xff000004, 0, 8);

        const output80 = getReport(devices, 'output', 0x80);
        assert_not_equals(output80, undefined, 'output report 0x80');
        checkReportUsage(output80, 0xff000005, 0, 8);

        const output82 = getReport(devices, 'output', 0x82);
        assert_not_equals(output82, undefined, 'output report 0x82');
        checkReportUsage(output82, 0xff000006, 0, 8);
      };

      checkReportMapSpeechMike = devices => {
        // Speech Mike exposes five HID interfaces, two of which are blocked by
        // WebHID. None of the interfaces use report IDs. Distinguish the
        // interfaces by their top-level collection usage information.
        assert_equals(devices.length, 3, 'device count');
        const device0 = getDeviceByCollectionUsage(devices, 0xffa0, 0x0001);
        assert_not_equals(device0, undefined, 'vendor device');
        assert_equals(device0.collections.length, 1,
                      'vendor device collection count');
        const device1 = getDeviceByCollectionUsage(devices, 0x000c, 0x0001);
        assert_not_equals(device1, undefined, 'consumer device');
        assert_equals(device1.collections.length, 1,
                      'consumer device collection count');
        const device2 = getDeviceByCollectionUsage(devices, 0x0001, 0x0004);
        assert_not_equals(device2, undefined, 'joystick device');
        assert_equals(device2.collections.length, 1,
                      'joystick device collection count');

        // These devices should be blocked by WebHID.
        const device3 = getDeviceByCollectionUsage(devices, 0x0001, 0x0002);
        assert_equals(device3, undefined, 'mouse device');
        const device4 = getDeviceByCollectionUsage(devices, 0x0001, 0x0006);
        assert_equals(device4, undefined, 'keyboard device');

        const device0Input = getReport([device0], 'input', 0x00);
        assert_not_equals(device0Input, undefined, 'vendor input report');
        checkReportUsage(device0Input, 0xffa10003, 0, 8);
        checkReportUsage(device0Input, 0xffa10004, 8, 8);

        const device0Output = getReport([device0], 'output', 0x00);
        assert_not_equals(device0Input, undefined, 'vendor output report');
        checkReportUsage(device0Output, 0xffa10005, 0, 8);
        checkReportUsage(device0Output, 0xffa10006, 8, 8);

        const device1Input = getReport([device1], 'input', 0x00);
        assert_not_equals(device1Input, undefined, 'consumer input report');
        checkReportUsage(device1Input, 0x000c00e9, 0, 1);
        checkReportUsage(device1Input, 0x000c00ea, 1, 1);

        const device2Input = getReport([device2], 'input', 0x00);
        assert_not_equals(device2Input, undefined, 'joystick input report');
        checkReportUsage(device2Input, 0x00090001, 0, 1);
        checkReportUsage(device2Input, 0x00090002, 1, 1);
        checkReportUsage(device2Input, 0x00090003, 2, 1);
        checkReportUsage(device2Input, 0x00090004, 3, 1);
        checkReportUsage(device2Input, 0x00090005, 4, 1);
        checkReportUsage(device2Input, 0x00090006, 5, 1);
        checkReportUsage(device2Input, 0x00090007, 6, 1);
        checkReportUsage(device2Input, 0x00090008, 7, 1);
        checkReportUsage(device2Input, 0x00090009, 8, 1);
        checkReportUsage(device2Input, 0x0009000a, 9, 1);
        checkReportUsage(device2Input, 0x0009000b, 10, 1);
        checkReportUsage(device2Input, 0x0009000c, 11, 1);
        checkReportUsage(device2Input, 0x0009000d, 12, 1);
        checkReportUsage(device2Input, 0x0009000e, 13, 1);
        checkReportUsage(device2Input, 0x0009000f, 14, 1);
        checkReportUsage(device2Input, 0x00090010, 15, 1);
        checkReportUsage(device2Input, 0x00090011, 16, 1);
        checkReportUsage(device2Input, 0x00090012, 17, 1);
        checkReportUsage(device2Input, 0x00090013, 18, 1);
        checkReportUsage(device2Input, 0x00090014, 19, 1);
        checkReportUsage(device2Input, 0x00010030, 24, 8);
        checkReportUsage(device2Input, 0x00010031, 32, 8);
      };

      checkReportMapEvolveLink = devices => {
        // Expect one device with three top-level collections.
        assert_equals(devices.length, 1, 'device count');
        assert_equals(devices[0].collections.length, 3, 'collection count');
        const collection0 = getCollectionByUsage(devices[0], 0x000b, 0x0005);
        assert_not_equals(collection0, undefined, 'headset collection');
        const collection1 = getCollectionByUsage(devices[0], 0xff00, 0x0005);
        assert_not_equals(collection1, undefined, 'vendor collection');
        const collection2 = getCollectionByUsage(devices[0], 0x000c, 0x0001);
        assert_not_equals(collection2, undefined, 'consumer collection');

        // Input reports
        const input01 = getReport(devices, 'input', 0x01);
        assert_not_equals(input01, undefined, 'input report 0x01');

        const input02 = getReport(devices, 'input', 0x02);
        assert_not_equals(input02, undefined, 'input report 0x02');
        checkReportUsage(input02, 0x000b0020, 0, 1);
        checkReportUsage(input02, 0x000b0097, 1, 1);
        checkReportUsage(input02, 0x000b002f, 2, 1);
        checkReportUsage(input02, 0x000b0021, 3, 1);
        checkReportUsage(input02, 0x000b0024, 4, 1);
        checkReportUsage(input02, 0x000b0050, 5, 1);

        // The following item is buggy in a way that causes it to differ by
        // platform. Here is the relevant portion of the report descriptor:
        //
        // 0x05, 0x0B,        //   Usage Page (Telephony)
        // ...  (some irrelevant items omitted)
        // 0x09, 0x07,        //   Usage (Programmable Button)
        // 0x05, 0x09,        //   Usage Page (Button)
        // 0x75, 0x01,        //   Report Size (1)
        // 0x95, 0x01,        //   Report Count (1)
        // 0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,...)
        //
        // The Input item on the last line emits a 1-bit report field. The usage
        // is defined by the preceding Usage Page and Usage items. Usage Page
        // items are global, meaning the page set by the first Usage Page item
        // persists until it is overridden by a following Usage Page item.
        // According to the HID specification for the Usage Page item, "Any
        // usage that follows which is defined as 16 bits or less is interpreted
        // as a Usage ID and concatenated with the Usage Page to form a 32 bit
        // Usage." This means the Usage should be concatenated with the
        // preceding Usage Page (Telephony) and not the following Usage Page
        // (Button).
        //
        // On platforms where we parse the raw HID report descriptor, we
        // correctly use the preceding Usage Page (Telephony). On Windows,
        // report descriptor information is parsed by the operating system and
        // provided to applications as "preparsed data". This parser incorrectly
        // applies the following Usage Page (Button).
        //
        // To avoid platform dependency, skip verifying this item.

        //checkReportUsage(input02, 0x000b0007, 6, 1);
        checkReportUsage(input02, 0x000b00b0, 7, 4);
        checkReportUsage(input02, 0x000b00b1, 7, 4);
        checkReportUsage(input02, 0x000b00b2, 7, 4);
        checkReportUsage(input02, 0x000b00b3, 7, 4);
        checkReportUsage(input02, 0x000b00b4, 7, 4);
        checkReportUsage(input02, 0x000b00b5, 7, 4);
        checkReportUsage(input02, 0x000b00b6, 7, 4);
        checkReportUsage(input02, 0x000b00b7, 7, 4);
        checkReportUsage(input02, 0x000b00b8, 7, 4);
        checkReportUsage(input02, 0x000b00b9, 7, 4);
        checkReportUsage(input02, 0x000b00ba, 7, 4);
        checkReportUsage(input02, 0x000b00bb, 7, 4);

        const input04 = getReport(devices, 'input', 0x04);
        assert_not_equals(input04, undefined, 'input report 0x04');
        checkReportUsage(input04, 0xff300020, 0, 1);
        checkReportUsage(input04, 0xff300097, 1, 1);
        checkReportUsage(input04, 0xff30002f, 2, 1);
        checkReportUsage(input04, 0xff300021, 3, 1);
        checkReportUsage(input04, 0xff300024, 4, 1);
        checkReportUsage(input04, 0xff30fffd, 5, 1);
        checkReportUsage(input04, 0xff300050, 6, 1);
        checkReportUsage(input04, 0xff3000b0, 7, 4);
        checkReportUsage(input04, 0xff3000b1, 7, 4);
        checkReportUsage(input04, 0xff3000b2, 7, 4);
        checkReportUsage(input04, 0xff3000b3, 7, 4);
        checkReportUsage(input04, 0xff3000b4, 7, 4);
        checkReportUsage(input04, 0xff3000b5, 7, 4);
        checkReportUsage(input04, 0xff3000b6, 7, 4);
        checkReportUsage(input04, 0xff3000b7, 7, 4);
        checkReportUsage(input04, 0xff3000b8, 7, 4);
        checkReportUsage(input04, 0xff3000b9, 7, 4);
        checkReportUsage(input04, 0xff3000ba, 7, 4);
        checkReportUsage(input04, 0xff3000bb, 7, 4);

        const input05 = getReport(devices, 'input', 0x05);
        assert_not_equals(input05, undefined, 'input report 0x05');
        checkReportUsage(input05, 0xff000001, 0, 8);

        const input08 = getReport(devices, 'input', 0x08);
        assert_not_equals(input08, undefined, 'input report 0x08');
        checkReportUsage(input08, 0xff600002, 0, 1);

        // Output reports
        const output02 = getReport(devices, 'output', 0x02);
        assert_not_equals(output02, undefined, 'output report 0x02');
        checkReportUsage(output02, 0x00080017, 0, 1);
        checkReportUsage(output02, 0x00080009, 1, 1);
        checkReportUsage(output02, 0x00080018, 2, 1);
        checkReportUsage(output02, 0x00080020, 3, 1);
        checkReportUsage(output02, 0x00080021, 4, 1);
        checkReportUsage(output02, 0x000b009e, 5, 1);

        const output04 = getReport(devices, 'output', 0x04);
        assert_not_equals(output04, undefined, 'output report 0x04');
        checkReportUsage(output04, 0xff400017, 0, 1);
        checkReportUsage(output04, 0xff400009, 1, 1);
        checkReportUsage(output04, 0xff400018, 2, 1);
        checkReportUsage(output04, 0xff400020, 3, 1);
        checkReportUsage(output04, 0xff400021, 4, 1);
        checkReportUsage(output04, 0xff30009e, 5, 1);

        const output05 = getReport(devices, 'output', 0x05);
        assert_not_equals(output05, undefined, 'output report 0x05');
        checkReportUsage(output05, 0xff000001, 0, 8);

        // Feature report 0x08
        const feature08 = getReport(devices, 'feature', 0x08);
        assert_not_equals(feature08, undefined, 'feature report 0x08');
        checkReportUsage(feature08, 0xff600002, 0, 1);
      };

      checkReportMapStadiaController = devices => {
        // Expect one device with one top-level collection.
        assert_equals(devices.length, 1, 'device count');
        assert_equals(devices[0].collections.length, 1, 'collection count');
        const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
        assert_not_equals(collection, undefined, 'game pad collection');

        // Input report 0x03
        const input03 = getReport(devices, 'input', 0x03);
        assert_not_equals(input03, undefined, 'input report 0x03');
        checkReportUsage(input03, 0x00010039, 0, 4);
        checkReportUsage(input03, 0x00090012, 8, 1);
        checkReportUsage(input03, 0x00090011, 9, 1);
        checkReportUsage(input03, 0x00090014, 10, 1);
        checkReportUsage(input03, 0x00090013, 11, 1);
        checkReportUsage(input03, 0x0009000d, 12, 1);
        checkReportUsage(input03, 0x0009000c, 13, 1);
        checkReportUsage(input03, 0x0009000b, 14, 1);
        checkReportUsage(input03, 0x0009000f, 15, 1);
        checkReportUsage(input03, 0x0009000e, 16, 1);
        checkReportUsage(input03, 0x00090008, 17, 1);
        checkReportUsage(input03, 0x00090007, 18, 1);
        checkReportUsage(input03, 0x00090005, 19, 1);
        checkReportUsage(input03, 0x00090004, 20, 1);
        checkReportUsage(input03, 0x00090002, 21, 1);
        checkReportUsage(input03, 0x00090001, 22, 1);
        checkReportUsage(input03, 0x00010030, 24, 8);
        checkReportUsage(input03, 0x00010031, 32, 8);
        checkReportUsage(input03, 0x00010032, 40, 8);
        checkReportUsage(input03, 0x00010035, 48, 8);
        checkReportUsage(input03, 0x000200c5, 56, 8);
        checkReportUsage(input03, 0x000200c4, 64, 8);
        checkReportUsage(input03, 0x000c00e9, 72, 1);
        checkReportUsage(input03, 0x000c00ea, 73, 1);
        checkReportUsage(input03, 0x000c00cd, 74, 1);

        // Output report 0x05
        const output05 = getReport(devices, 'output', 0x05);
        assert_not_equals(output05, undefined, 'output report 0x05');
        checkReportUsage(output05, 0x000f0097, 0, 16);
      };

      // The content of the HIDDevice.collections member can differ by platform,
      // so this test aims to only test the properties that are expected to
      // remain consistent. In particular, the test verifies that all reports
      // are present and that each report field with an assigned usage appears
      // at the correct bit index and has the correct field bit width.
      manual_hid_test(async (t, devices) => {
        if (hasDeviceIds(devices, 0x054c, 0x09cc))
          checkReportMapDualshock4(devices);
        else if (hasDeviceIds(devices, 0x054c, 0x0ce6))
          checkReportMapDualSense(devices);
        else if (hasDeviceIds(devices, 0x057e, 0x2009))
          checkReportMapSwitchPro(devices);
        else if (hasDeviceIds(devices, 0x0911, 0x0fa0))
          checkReportMapSpeechMike(devices);
        else if (hasDeviceIds(devices, 0x0b0e, 0x0306))
          checkReportMapEvolveLink(devices);
        else if (hasDeviceIds(devices, 0x18d1, 0x9400))
          checkReportMapStadiaController(devices);
        else
          assert_unreached('Select a supported device.');
      }, 'Collection info matches the expected report map.');
    </script>
  </body>
</html>
